﻿#include "WeatherTimer2.h"
#include "OCMain/cpptime.h"
#include "WeatherUtil.h"
#include <typeinfo>

WeatherTimer2::WeatherTimer2()
{
	auto getClassName = [this]() {
		String name = typeid(*this).name();
		return name.substr(name.find_last_of(" ") + 1, name.length());
		};

	WeatherDB::DbName = "WeatherTimer2";
}

WeatherTimer2::~WeatherTimer2()
{

}

bool WeatherTimer2::init()
{
	if (dataPath1.empty())
	{
		LOG_ERROR << dataPath1 << " data path invalid.\n";
		return false;
	}

	if (cachePath.empty())
	{
		LOG_ERROR << cachePath << " cache path invalid.\n";
		return false;
	}

	FileUtil::formatPathNoSeperator(cachePath);
	FileUtil::formatPathNoSeperator(dataPath1);
	FileUtil::formatPathNoSeperator(dataPath2);
	FileUtil::formatPathNoSeperator(dataPath3);

	if (!FileUtil::existFile(cachePath))
	{
		FileUtil::makeDirectory(cachePath);
	}

	WeatherDB::Path = cachePath;

	String dbFileName = joinPath(cachePath, WeatherDB::DbName + ".db");

	LOG_INFO << "DataPath1:" << dataPath1 << "\n";
	LOG_INFO << "DataPath2:" << dataPath2 << "\n";
	LOG_INFO << "DataPath3:" << dataPath3 << "\n";
	LOG_INFO << "CachePath:" << cachePath << "\n";
	LOG_INFO << "Database:" << dbFileName << "\n";

	globalConfigMap[VolumeOptions::Config::NameWriter] = VolumeOptions::Config::ValueD3Writer;
	globalConfigMap[VolumeOptions::Config::NameGZipCompress] = "false";
	globalConfigMap[VolumeOptions::Config::NameMakeDirForFile] = "true";
	globalConfigMap[VolumeOptions::Config::NameDimSeperator] = "/";

	WeatherUtil::createDBClient(dbFileName, WeatherDB::DbName);

	return true;
}

inline String formatTimeStamp(String date, String hhmmss)
{
	return str_format("%s-%s-%s %s:%s:%s",
		date.substr(0, 4).c_str(),
		date.substr(4, 2).c_str(),
		date.substr(6, 2).c_str(),
		hhmmss.substr(0, 2).c_str(),
		hhmmss.substr(2, 2).c_str(),
		hhmmss.substr(4, 2).c_str()
	);
}

inline auto extract3D(NetCDFReader& reader, StringMap& configMap, String& varName, String& encRange)
{
	return std::async(std::launch::async, [&reader, &configMap, &varName, &encRange]() {
		auto copyMap = configMap;
		copyMap[VolumeOptions::Config::NameEncodeRange] = encRange;
		reader.extract(varName, copyMap);
		});
}

inline auto extractWind(String& ncFileName, String& nameU, String& nameV, StringMap& configMap)
{
	return std::async(std::launch::async, [&ncFileName, &nameU, &nameV, &configMap]() {
		auto copyMap = configMap;
		copyMap[VolumeOptions::Config::NameEncodeRange] = VolumeEncoder::EncodeRangeToString(VolumeEncoder::EncodeRangeWind);
		NetCDFReader::extract({ {ncFileName,nameU},{ncFileName,nameV} }, std::make_shared<NcAlgorithmVecLength>(), copyMap);
		});
}

inline auto extractUV(String& ncFileName, String& nameU, String& nameV, StringMap& configMap)
{
	return std::async(std::launch::async, [&ncFileName, &nameU, &nameV, &configMap]() {
		auto copyMap = configMap;
		copyMap[VolumeOptions::Config::NameEncodeRange] = VolumeEncoder::EncodeRangeToString(VolumeEncoder::EncodeRangeWind);
		NetCDFReader::extract({ {ncFileName,nameU},{ncFileName,nameV} }, std::make_shared<NcAlgorithmUV>(), copyMap, 0);
		});
}

inline auto extractCR(String& ncFileName, String& nameDBZ, StringMap& configMap)
{
	return std::async(std::launch::async, [&ncFileName, &nameDBZ, &configMap]() {
		auto copyMap = configMap;
		copyMap[VolumeOptions::Config::NameEncodeRange] = VolumeEncoder::EncodeRangeToString(VolumeEncoder::EncodeRangeDBZ);
		NetCDFReader::extract({ {ncFileName ,nameDBZ} }, std::make_shared<NcAlgorithmCR>(), copyMap);
		});
}

std::vector<std::future<void>> WeatherTimer2::makeFutures(NetCDFReader& reader, String& ncFileName, StringMap& configMap, WeatherTimer2::VarNameTuple& vars)
{
	auto& [nameDBZ, nameRH, nameTEMP, nameW, nameU, nameV] = vars;

	static auto encRangeDBZ = VolumeEncoder::EncodeRangeToString(VolumeEncoder::EncodeRangeDBZ);
	static auto encRangeRH = VolumeEncoder::EncodeRangeToString(VolumeEncoder::EncodeRangePercent);
	static auto encRangeW = String("-5.0f,5.0f");
	static auto encRangeTemp = String("-10.0f,10.0f");

	std::vector<std::future<void>> futures;

	futures.push_back(extract3D(reader, configMap, nameDBZ, encRangeDBZ));
	futures.push_back(extract3D(reader, configMap, nameRH, encRangeRH));
	futures.push_back(extract3D(reader, configMap, nameW, encRangeW));
	futures.push_back(extract3D(reader, configMap, nameTEMP, encRangeTemp));
	futures.push_back(extractWind(ncFileName, nameU, nameV, configMap));
	futures.push_back(extractUV(ncFileName, nameU, nameV, configMap));
	futures.push_back(extractCR(ncFileName, nameDBZ, configMap));

	return futures;
}

void WeatherTimer2::clearCaches(String dataType)
{
	for (int i = cacheExpiredTime; i < 10; i++)
	{
		String oldDate = WeatherUtil::getDateUTC(i);
		String path = joinPath(cachePath, dataType, oldDate);
		if (FileUtil::existFile(path))
			WeatherUtil::removeDir(path);
	}
}

void WeatherTimer2::doJob3DView()
{
	static String dateType = "3dview";
	static StringMap cookies;

	static String varName = "Tracer";
	static String encRangeDBZ = VolumeEncoder::EncodeRangeToString(VolumeEncoder::EncodeRangeDBZ);
	static StringMap configMap = globalConfigMap;

	String cacehPath3DView = joinPath(cachePath, dateType);

	StringVector newFileVec;
	StringVector allFileVec;

	String dataPath = dataPath3;
	StringVector nc3dVec = WeatherUtil::listFileWithExt(dataPath, "_3d.nc");

	for (auto nc3dFile : nc3dVec)
	{
		if (cookies.find(nc3dFile) == cookies.end())
		{
			newFileVec.push_back(nc3dFile);
		}
		allFileVec.push_back(nc3dFile);
	}

	if (!newFileVec.empty())
	{
		StringVector caches = WeatherUtil::listSubDir(cacehPath3DView);
		for (auto nc3dFile : caches)
		{
			if (find(allFileVec.begin(), allFileVec.end(), nc3dFile) == allFileVec.end())
			{
				WeatherUtil::removeDir(joinPath(cacehPath3DView, nc3dFile));
				cookies.erase(nc3dFile);
			}
			else
			{
				cookies[nc3dFile] = "";
			}
		}

		for (auto nc3dFile : newFileVec)
		{
			if (cookies.find(nc3dFile) != cookies.end())continue;
			String ncFileName = joinPath(dataPath, nc3dFile);
			NetCDFReader reader;
			if (!reader.open(ncFileName))
			{
				continue;
			}
			configMap[VolumeOptions::Config::NameOutputPath] = joinPath(cacehPath3DView, nc3dFile);
			extract3D(reader, configMap, varName, encRangeDBZ);
		}
	}
}
void WeatherTimer2::doJobHist(String date)
{
	static String dateTypeAnal = Anal3kmData::tableName;
	static String dateTypeFcst = Fcst3kmData::tableName;

	LOG_INFO << "doJobHist:" << date << "\n";

	std::future<void> f1 = std::async(std::launch::async, [this, &date]() {
		String todayDataPath1 = joinPath(dataPath2, "3km/nc/anal", date);
		String todayCachePath = joinPath(cachePath, dateTypeAnal, date);
		extractAnal<Anal3kmData>(todayDataPath1, todayCachePath, date);
		});

	std::future<void> f2 = std::async(std::launch::async, [this, &date]() {
		String todayPath = joinPath("3km/nc/fcst", date);
		StringVector hourPathVec = WeatherUtil::listSubDir(joinPath(dataPath2, todayPath));
		for (auto hourFolder : hourPathVec)
		{
			String todayDataPath1 = joinPath(dataPath2, todayPath, hourFolder);
			String todayCachePath = joinPath(cachePath, dateTypeFcst, date, hourFolder);
			extractFcst<Fcst3kmData>(todayDataPath1, todayCachePath, date, hourFolder);
		}
		});
	f1.get();
	f2.get();
}

void WeatherTimer2::start(int duaration11)
{
	if (!init())
	{
		return;
	}

	duaration = duaration11;

	drogon::app().getLoop()->runAfter(0, [this]() {
		drogon::orm::DbClientPtr dbClient = drogon::app().getDbClient(WeatherDB::DbName);
		try {
			String sql1 = "CREATE TABLE IF NOT EXISTS fcst3km(\
				id INTEGER PRIMARY KEY,\
				g INTEGER,\
				timestamp BIGINT)";
			String sql2 = "CREATE TABLE IF NOT EXISTS anal3km(\
				timestamp BIGINT PRIMARY KEY)";
			dbClient->execSqlSync(sql1);
			dbClient->execSqlSync(sql2);
			String sql3 = "CREATE TABLE IF NOT EXISTS fcst1km(\
				id INTEGER PRIMARY KEY,\
				g INTEGER,\
				timestamp BIGINT)";
			String sql4 = "CREATE TABLE IF NOT EXISTS anal1km(\
				timestamp BIGINT PRIMARY KEY)";
			dbClient->execSqlSync(sql3);
			dbClient->execSqlSync(sql4);
		}
		catch (drogon::orm::DrogonDbException& ex) {
			LOG_ERROR << ex.base().what() << "\n";
		}
		catch (...) {
			LOG_ERROR << "unknow error.\n";
			return;
		}

		static CppTime::Timer timer1;
		static CppTime::Timer timer2;
		static CppTime::Timer timer3;
		static CppTime::Timer timer4;
		static CppTime::Timer timer5;

		timer1.add(std::chrono::seconds(0), [this](CppTime::timer_id) {
			static String lastDate = "";
			doJobAnal<Anal3kmData>(lastDate);
			}, std::chrono::seconds(duaration));
		timer2.add(std::chrono::seconds(0), [this](CppTime::timer_id) {
			static String lastDate = "";
			doJobFcst<Fcst3kmData>(lastDate);
			}, std::chrono::seconds(duaration));
		timer3.add(std::chrono::seconds(0), [this](CppTime::timer_id) {
			doJob3DView();
			}, std::chrono::seconds(duaration));
		timer4.add(std::chrono::seconds(0), [this](CppTime::timer_id) {
			static String lastDate = "";
			doJobAnal<Anal1kmData>(lastDate);
			}, std::chrono::seconds(duaration));
		timer5.add(std::chrono::seconds(0), [this](CppTime::timer_id) {
			static String lastDate = "";
			doJobFcst<Fcst1kmData>(lastDate);
			}, std::chrono::seconds(duaration));

		if (!histVec.empty())
		{
			static CppTime::Timer timer6;
			timer6.add(std::chrono::seconds(0), [this](CppTime::timer_id id) {
				for (auto date : histVec)
				{
					doJobHist(date);
				}
				timer6.remove(id);
				});
		}

		});
	LOG_INFO << "WeatherTimer2 run in Duaration(" << duaration << ")\n";
}

